#include "StdAfx.h"
#include "Terrain.h"
#include "Level.h"
#include "GraphCamera.h"
#include "PhysX.h"
#include <list>
#include <algorithm>
#include <boost/foreach.hpp>

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

Terrain::Terrain(Level *pLevel, InitInfo &initinfo)
	:LevelElement()
	,m_FileName(_T(""))
	,m_TextureFileName(_T(""))
	,m_NumColumns(0)
	,m_NumRows(0)
	,m_TileSize(0)
	,m_ScaleHeight(0)
	,m_Offset(0)
	,m_pVertexLayout(0)
	,m_pVertexBuffer(0)
	,m_pIndexBuffer(0)
	,m_pDefaultEffect(0)	
	,m_pDefaultTechnique(0)	
	,m_pTextureRV(0)
	,m_pWorldViewProjectionVariable(NULL)
	,m_IntInfo(initinfo)
	,mfxLightWVPVar(0)
	,m_OrthoCam(0)
	,mfxLightVar(0)
{
	m_pLevel = pLevel;
}

Terrain::~Terrain(void)
{
	SafeRelease(m_pVertexBuffer );
	SafeRelease(m_pVertexLayout);
	SafeRelease(m_pIndexBuffer);
}
void Terrain::Initialize(ContentManager *pContentManager)
{
	m_FileName = m_IntInfo.HeightmapFilename;
	m_TextureFileName = m_IntInfo.LayerMapFilename0;
	m_NumColumns = m_IntInfo.NumCols;
	m_NumRows = m_IntInfo.NumCols;
	m_TileSize = m_IntInfo.CellSpacing;
	m_ScaleHeight = m_IntInfo.HeightScale;
	m_Offset = m_IntInfo.HeightOffset;
	m_bCastShadow = m_IntInfo.CastShadows;

	CreateEffect(pContentManager);
	m_pTextureRV = pContentManager->GetTexture(m_pLevel->GetDevice(), m_TextureFileName);
	mLayer0 = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.LayerMapFilename0);
	mLayer1 = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.LayerMapFilename1);
	mLayer2 = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.LayerMapFilename2);
	mLayer3 = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.LayerMapFilename3);
	mLayer4 = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.LayerMapFilename4);
	mBlendMap = pContentManager->GetTexture(m_pLevel->GetDevice(), m_IntInfo.BlendMapFilename);

	if(LoadHeightMap())
	{
		Smooth();
		CreateVerticesAndTriangles();
		DefineInputlayout();
		BuildVertexBuffer();
		BuildIndexBuffer();
		CreatePhysicsTerrain();

	}
}
void Terrain::DrawToShadowmap(GraphCamera* pOrtohCam, D3DXMATRIX &mLightVolume
	,ID3D10Effect* BuildShadowMapFX)
{
	m_OrthoCam = pOrtohCam;

	ID3D10EffectTechnique* m_pBuildShadowTechnique;

	m_pBuildShadowTechnique = BuildShadowMapFX->GetTechniqueByName("BuildShadowMapTech");
	if(!m_pBuildShadowTechnique->IsValid())MessageBox(0,_T("Technique not valid"),_T("ERROR"),0);

	if(mfxBuildShadowMapDiffuseMapVar == 0)
		mfxBuildShadowMapDiffuseMapVar = BuildShadowMapFX->GetVariableByName("gDiffuseMap")->AsShaderResource();
	if(mfxBuildShadowMapLightWVPVar == 0)
		mfxBuildShadowMapLightWVPVar   = BuildShadowMapFX->GetVariableByName("gLightWVP")->AsMatrix();
	
	Light temp;
	RenderContext renderContext(pOrtohCam, temp);

	D3D10_TECHNIQUE_DESC techDesc;
    m_pBuildShadowTechnique->GetDesc( &techDesc );
	m_pLevel->GetDevice()->IASetInputLayout( m_pVertexLayout );

    for(UINT i = 0; i < techDesc.Passes; ++i)
    {
        ID3D10EffectPass* pass = m_pBuildShadowTechnique->GetPassByIndex(i);

		D3DXMATRIX floorWVP = m_World*pOrtohCam->GetViewProj();
		mfxBuildShadowMapLightWVPVar->SetMatrix((float*)&floorWVP);
		mfxBuildShadowMapDiffuseMapVar->SetResource(m_pTextureRV);
		pass->Apply(0);
		//Draw

		Draw(&renderContext, mLayer0);
    }
}
void Terrain::Draw(const RenderContext* pRenderContext, ID3D10ShaderResourceView* shadowdepthmap)
{
	if(!m_pDefaultTechnique )
	{
		MessageBox(0,_T("No Technique"),_T("ERROR"),0);
		return;
	}
	if(!m_pVertexBuffer)
	{
		MessageBox(0,_T("No Vertices"),_T("ERROR"),0);
		return;
	}	
	// Update Shader matrix variables
	D3DXMATRIX matView = pRenderContext->GetCamera()->GetView();
	D3DXMATRIX matProj = pRenderContext->GetCamera()->GetProj();
	m_pWorldViewProjectionVariable->SetMatrix((float*)&(m_World* pRenderContext->GetCamera()->GetViewProj()));
	m_pWorldVariable->SetMatrix((float*)&(m_World));

	///////
	//////
	// Aanpassen naar ortho
	if(m_OrthoCam != 0)
		mfxLightWVPVar->SetMatrix((float*)&(m_World*m_OrthoCam->GetViewProj()));
	// Set the input layout
    m_pLevel->GetDevice()->IASetInputLayout( m_pVertexLayout );

	//set the texture variabele
	mfxLayer0Var->SetResource(mLayer0);
	mfxLayer1Var->SetResource(mLayer1);
	mfxLayer2Var->SetResource(mLayer2);
	mfxLayer3Var->SetResource(mLayer3);
	mfxLayer4Var->SetResource(mLayer4);
	mfxBlendMapVar->SetResource(mBlendMap);
	mfxShadowDepthMap->SetResource(shadowdepthmap);

	D3DXVECTOR3 lookvec = pRenderContext->GetCamera()->GetLook();
	float lightDir[3] = {lookvec.x, lookvec.y, lookvec.z};//{0.5f,-1.0f,0.5f};
	//m_pLightDirectionVariabele->SetFloatVector((float*)&lightDir);

	D3DXVECTOR3 pos = pRenderContext->GetCamera()->GetPos();
	m_pEyePosVariable->SetRawValue(&pos,0,sizeof(D3DXVECTOR3));

	mfxLightVar->SetRawValue(&pRenderContext->GetLight(), 0, sizeof(Light));

	// Set vertex buffer(s)
    UINT offset = 0;
	UINT vertexBufferStride = sizeof(VertexPosNormTex);
    m_pLevel->GetDevice()->IASetVertexBuffers( 0, 1, &m_pVertexBuffer, &vertexBufferStride, &offset );
   	
	// Set index buffer
	m_pLevel->GetDevice()->IASetIndexBuffer(m_pIndexBuffer,DXGI_FORMAT_R32_UINT,0);

    // Set primitive topology
    m_pLevel->GetDevice()->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );



    D3D10_TECHNIQUE_DESC techDesc;
    m_pDefaultTechnique->GetDesc( &techDesc );
    for( UINT p = 0; p < techDesc.Passes; ++p )
    {
        m_pDefaultTechnique->GetPassByIndex(p)->Apply(0);
		m_pLevel->GetDevice()->DrawIndexed( m_VecIndices.size(), 0, 0 ); 
    }
}
void Terrain::CreateEffect(ContentManager *pContentManager)
{
	m_pDefaultEffect = pContentManager->GetEffect(m_pLevel->GetDevice(),  _T("./Effect/Terrain.fx"));
	//get first technique found
	m_pDefaultTechnique = m_pDefaultEffect->GetTechniqueByName("TechAnisotropic");
	if(!m_pDefaultTechnique->IsValid())MessageBox(0,_T("Technique not valid"),_T("ERROR"),0);

	GetEffectVariables(m_pDefaultEffect);
}
void Terrain::GetEffectVariables(ID3D10Effect* pEffect)
{
	mfxLightVar = pEffect->GetVariableByName("gLight");

	//get effect variables
	m_pWorldViewProjectionVariable = pEffect->GetVariableByName("gWVP")->AsMatrix();
	if(!m_pWorldViewProjectionVariable->IsValid())MessageBox(0,_T("Getting EffectSemantic WorldViewProjection Failed"),_T("ERROR"),0);

	m_pWorldVariable = pEffect->GetVariableByName("gWorld")->AsMatrix();
	if(!m_pWorldVariable->IsValid())MessageBox(0,_T("Getting EffectSemantic World Failed"),_T("ERROR"),0);

	m_pEyePosVariable = pEffect->GetVariableByName("EyePos")->AsVector();

	mfxLayer0Var = pEffect->GetVariableByName( "gLayer0" )->AsShaderResource();
	mfxLayer1Var = pEffect->GetVariableByName( "gLayer1" )->AsShaderResource();
	mfxLayer2Var = pEffect->GetVariableByName( "gLayer2" )->AsShaderResource();
	mfxLayer3Var = pEffect->GetVariableByName( "gLayer3" )->AsShaderResource();
	mfxLayer4Var = pEffect->GetVariableByName( "gLayer4" )->AsShaderResource();
	mfxBlendMapVar = pEffect->GetVariableByName( "gBlendMap" )->AsShaderResource();
	mfxShadowDepthMap = pEffect->GetVariableByName( "gShadowMap" )->AsShaderResource();

	mfxLightWVPVar = pEffect->GetVariableByName("gLightWVP")->AsMatrix();
}

unsigned int Terrain::GetNumVertices()
{
	return m_NumColumns*m_NumRows;
}
	

unsigned int Terrain::GetNrOfTriangles()
{
	return 2 * (m_NumColumns-1) * (m_NumRows-1);
}

bool Terrain::LoadHeightMap(){
	
	//create already sized vector to store height values
	m_VecHeightValues.resize( GetNumVertices(), 0 );
	//open binary file 
	std::ifstream inFile;
	inFile.open(m_FileName.c_str(),std::ios_base::binary);
	if ( !inFile )
	{
		MessageBox(0,L"Terrain file not found", _T("ERROR"),0);
		return false;
	}
	//read height info
	inFile.read((char*)&m_VecHeightValues[0], (std::streamsize)(GetNumVertices() * sizeof(unsigned short)) );	
	inFile.close();

	return true;
}

void Terrain::CreateVerticesAndTriangles()
{

	//Create DirectX10 Mesh 5.15
	int numQuadX = m_NumRows-1;
	int numQuadZ = m_NumColumns-1;
	double gridWidth = numQuadX * m_TileSize;
	double gridDepth = numQuadZ * m_TileSize;


	//create a grid, centered around origin
	//fill vertex vector
	m_VecVertices.reserve(GetNumVertices());
	vector<pair<int, D3DXVECTOR4>> normallist;
	normallist.reserve(GetNumVertices());

	float du = 1.0f / (m_IntInfo.NumCols-1);
	float dv = 1.0f / (m_IntInfo.NumRows-1);

	for(unsigned int row=0;row<m_NumRows;row++)
	{
		for(unsigned int col=0;col<m_NumColumns;col++)
		{
			VertexPosNormTex v;
			v.pos.x = float( m_TileSize*col);
			v.pos.y = float(m_VecHeightValues[row*m_NumColumns+col]*m_ScaleHeight+m_Offset);
			v.pos.z = float( m_TileSize*row);
			v.tex.x = float(col)/m_NumColumns;
			v.tex.y = float(row)/m_NumRows;

			/*D3DXVECTOR3 temp = D3DXVECTOR3(0.0f,1.0f,0);
			D3DXVec3Normalize(&temp,&temp);
			v.normal = temp;*/
			//float xcoord =  -gridWidth/2 + m_TileSize*x;
			//float zcoord = -gridDepth/2 + m_TileSize*z;
			//float ycoord = m_VecHeightValues[x+z*m_NumRows];
			//float u = x/m_NumColumns;
			//float v = z/m_NumRows;
			//VertexPosTex v(xcoord, ycoord, zcoord,u,v);
			m_VecVertices.push_back(v);
		}
	}

	//create triangles
	unsigned int numTriangles = numQuadX * numQuadZ * 6;
	//resize vector for speed purposes
	m_VecIndices.reserve(numTriangles);

	for(unsigned int row = 0; row < (m_NumRows-1); ++row)
	{
		for(unsigned int column = 0 ; column < (m_NumColumns-1); ++column)
		{
			int index1 = row * m_NumColumns + column;
			int index2 = index1 + 1;

			int index3 = index1 + m_NumColumns;
			int index4 = index3 + 1;

			AddQuad(index1,index2, index3, index4);
			//Eventueel hier code toevoegen om dan de normal ineens te berekenen
			//In plaats van hieronder
			CalcNormal(index1, index3, index4, normallist);
			CalcNormal(index1, index4, index2, normallist);
			//AddTriangle(a,c,d);
			//AddTriangle(a,d,b);
		} 
	}
	///////////////////////////////////
	//// De normallijst doorlopen en berekenen
	//// Smoothing
	vector<pair<VertexPosNormTex, int>> templist;
	vector<pair<int, D3DXVECTOR4>>::iterator counter;
	vector<VertexPosNormTex>::iterator pos;
	vector<pair<VertexPosNormTex, int>>::iterator pos2;

	for(pos = m_VecVertices.begin(); pos != m_VecVertices.end(); ++pos)
	{
		templist.push_back(make_pair((*pos), 0));
	}
	for(counter = normallist.begin(); counter != normallist.end(); ++counter)
	{
		pos2 = templist.begin();
		advance(pos2, counter->first);
		(*pos2).first.normal.x += (*counter).second.x;
		(*pos2).first.normal.y += (*counter).second.y;
		(*pos2).first.normal.z += (*counter).second.z;
		(*pos2).second += 1;
	}
	for(pos2 = templist.begin(); pos2 != templist.end(); ++pos2)
	{
		(*pos2).first.normal /= (*pos2).second;
	}
	int teller = 0;
	for(pos2 = templist.begin(); pos2 != templist.end(); ++pos2)
	{
		pos = m_VecVertices.begin();
		advance(pos, teller);
		(*pos).normal = (*pos2).first.normal;
		++teller;
	}
}
vector<pair<int, D3DXVECTOR4>>::iterator Terrain::FindInFirst(vector<pair<int, D3DXVECTOR4>> &list, int nr)
{
	vector<pair<int, D3DXVECTOR4>>::iterator it;
	for(it = list.begin(); it != list.end(); ++it)
	{
		if(it->first == nr)
			return it;
	}
	return it;
}
void Terrain::CalcNormal(unsigned int a, unsigned int b, unsigned int c, vector<pair<int, D3DXVECTOR4>> &normallist)
{
	vector<VertexPosNormTex>::iterator vertex;

	vertex = m_VecVertices.begin();
	advance(vertex, a);
	VertexPosNormTex &p1 = (*vertex);

	vertex = m_VecVertices.begin();
	advance(vertex, b);
	VertexPosNormTex &p2 = (*vertex);

	vertex = m_VecVertices.begin();
	advance(vertex, c);
	VertexPosNormTex &p3 = (*vertex);

	D3DXVECTOR3 normal(0,1,0);
	D3DXVECTOR3 v1 = p2.pos - p1.pos;
	D3DXVECTOR3 v2 = p3.pos - p1.pos;

	D3DXVec3Cross(&normal, &v1, &v2);
	D3DXVec3Normalize(&normal,&normal);

	normallist.push_back(make_pair(a,D3DXVECTOR4(normal.x,normal.y, normal.z, 1)));
	normallist.push_back(make_pair(b,D3DXVECTOR4(normal.x,normal.y, normal.z, 1)));
	normallist.push_back(make_pair(c,D3DXVECTOR4(normal.x,normal.y, normal.z, 1)));

	//Als er geen smooth gebruikt wordt
	p1.normal = normal;
	p2.normal = normal;
	p3.normal = normal;
}
void Terrain::AddTriangle (unsigned int a, unsigned int b, unsigned c)
{
	m_VecIndices.push_back(a);
	m_VecIndices.push_back(b);
	m_VecIndices.push_back(c);
}

void Terrain::AddQuad (unsigned int a, unsigned int b, unsigned c, unsigned d)
{
	AddTriangle(a,c,d);
	AddTriangle(a,d,b);
}
void Terrain::DefineInputlayout()
{
	// Define the input layout DO NOT USE D3D10_APPEND_ALIGNED_ELEMENT 
	D3D10_INPUT_ELEMENT_DESC layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		 { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0 },
	};
	UINT numElements = sizeof(layout)/sizeof(layout[0]);

    // Create the input layout
    D3D10_PASS_DESC PassDesc;
	// Get the pass decriptor from the effect technique
    m_pDefaultTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
    HR(m_pLevel->GetDevice()->CreateInputLayout( layout, numElements, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &m_pVertexLayout ));

}

void Terrain::BuildVertexBuffer()
{
	//fill a buffer description to copy the vertexdata into graphics memory
	D3D10_BUFFER_DESC bd;
	bd.Usage = D3D10_USAGE_DEFAULT;
	bd.ByteWidth = sizeof( VertexPosNormTex ) * m_VecVertices.size();
	bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = 0;
	bd.MiscFlags = 0;

	D3D10_SUBRESOURCE_DATA initData;
	initData.pSysMem = m_VecVertices.data();
	//create a ID3D10Buffer in graphics memory containing the vertex info
	HR(m_pLevel->GetDevice()->CreateBuffer( &bd, &initData, &m_pVertexBuffer ));
}
void Terrain::BuildIndexBuffer()
{
	D3D10_BUFFER_DESC ibd;
    ibd.Usage = D3D10_USAGE_IMMUTABLE;
    ibd.ByteWidth = sizeof(DWORD) * m_VecIndices.size();
    ibd.BindFlags = D3D10_BIND_INDEX_BUFFER;
    ibd.CPUAccessFlags = 0;
    ibd.MiscFlags = 0;
    D3D10_SUBRESOURCE_DATA initData;
    initData.pSysMem = m_VecIndices.data();
    HR(m_pLevel->GetDevice()->CreateBuffer(&ibd, &initData, &m_pIndexBuffer));
}

void Terrain::CreatePhysicsTerrain()
{
	 NxHeightFieldDesc heightFieldDesc;    
	 heightFieldDesc.nbColumns           = m_NumColumns;    
	 heightFieldDesc.nbRows              = m_NumRows;    
	 heightFieldDesc.verticalExtent      = -10;    
	 heightFieldDesc.convexEdgeThreshold = 0;    
	 //Allocate storage for samples.    
	 heightFieldDesc.samples             = new NxU32[m_NumColumns*m_NumRows];    
	 heightFieldDesc.sampleStride        = sizeof(NxU32); 
	 
	 NxU8* currentByte = (NxU8*)heightFieldDesc.samples;    
	 for (NxU32 row = 0; row < m_NumRows; row++)
	 {        
		 for (NxU32 column = 0; column < m_NumColumns; column++)            
		 {            
			 NxHeightFieldSample* currentSample = (NxHeightFieldSample*)currentByte;
			 currentSample->height         = m_VecHeightValues[column*m_NumRows+row]/2; //from unsigned to signed, prevent clipping
			 //Desired height value. Signed 16 bit integer.           
			 currentSample->materialIndex0 = 0;            
			 currentSample->materialIndex1 = 0;            
			 currentSample->tessFlag = 0;            
			 currentByte += heightFieldDesc.sampleStride;
		 }        
	 }    
	 NxHeightField* heightField = m_pLevel->GetPhysicsScene()->getPhysicsSDK().createHeightField(heightFieldDesc);    

	
	 //Data has been copied therefore free the buffer.    
	 delete[] heightFieldDesc.samples;

	 NxHeightFieldShapeDesc heightFieldShapeDesc;   
	 heightFieldShapeDesc.heightField     = heightField;    
	 heightFieldShapeDesc.heightScale     = m_ScaleHeight*2;//undo division in currentSample->height  
	 heightFieldShapeDesc.rowScale        = m_TileSize;
	 heightFieldShapeDesc.columnScale     = m_TileSize;    
	 heightFieldShapeDesc.materialIndexHighBits = 0;    
	 heightFieldShapeDesc.holeMaterial = -1;
	 //heightFieldShapeDesc.
	 NxActorDesc actorDesc;    
	 actorDesc.shapes.pushBack(&heightFieldShapeDesc);    
	 actorDesc.globalPose.t = NxVec3(0,m_Offset,0);    
	 NxActor* newActor = m_pLevel->GetPhysicsScene()->createActor(actorDesc);

}
void Terrain::Smooth()
{
	std::vector<unsigned short> dest( m_VecHeightValues.size() );

	for(UINT i = 0; i < m_IntInfo.NumRows; ++i)
	{
		for(UINT j = 0; j < m_IntInfo.NumCols; ++j)
		{
			dest[i*m_IntInfo.NumCols+j] = Average(i,j);
		}
	}

	// Replace the old heightmap with the filtered one.
	m_VecHeightValues = dest;
}
float Terrain::Average(UINT i, UINT j)
{
	// Function computes the average height of the ij element.
	// It averages itself with its eight neighbor pixels.  Note
	// that if a pixel is missing neighbor, we just don't include it
	// in the average--that is, edge pixels don't have a neighbor pixel.
	//
	// ----------
	// | 1| 2| 3|
	// ----------
	// |4 |ij| 6|
	// ----------
	// | 7| 8| 9|
	// ----------

	float avg = 0.0f;
	float num = 0.0f;

	for(UINT m = i-1; m <= i+1; ++m)
	{
		for(UINT n = j-1; n <= j+1; ++n)
		{
			if( InBounds(m,n) )
			{
				avg += m_VecHeightValues[m*m_IntInfo.NumCols + n];
				num += 1.0f;
			}
		}
	}

	return avg / num;
}
bool Terrain::InBounds(UINT i, UINT j)
{
	// True if ij are valid indices; false otherwise.
	return 
		i >= 0 && i < m_IntInfo.NumRows && 
		j >= 0 && j < m_IntInfo.NumCols;
}